package com.mallcloud.mall.app.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.mallcloud.mall.app.api.constant.ApiConstants;
import com.mallcloud.mall.app.api.entity.dto.WxPay2UnifiedOrderInfoDTO;
import com.mallcloud.mall.app.api.entity.vo.WechatPayV2Response;
import com.mallcloud.mall.app.api.entity.vo.WxPay2NotifyVO;
import com.mallcloud.mall.app.api.entity.vo.WxPay2UnifiedOrderInfoVO;
import com.mallcloud.mall.app.api.entity.vo.WxPay2UnifiedOrderResponseVO;
import com.mallcloud.mall.app.config.WechatPayV2Config;
import com.mallcloud.mall.app.service.AppOrderCouponService;
import com.mallcloud.mall.app.service.AppWxPay2Service;
import com.mallcloud.mall.common.core.constant.enums.RLockEnum;
import com.mallcloud.mall.common.core.constant.enums.mall.OrderEnum;
import com.mallcloud.mall.common.core.exception.FailException;
import com.mallcloud.mall.common.core.util.HttpUtil;
import com.mallcloud.mall.common.core.util.XmlParseUtil;
import com.mallcloud.mall.mall.api.entity.MallOrderCouponModel;
import com.mallcloud.mall.mall.service.MallOrderCouponService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 微信支付
 */
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty("const.wxpay.v2.appId")
public class AppWxPay2ServiceImpl implements AppWxPay2Service {

	private final HttpUtil httpUtil;
	private final Redisson redisson;
	private final WechatPayV2Config wechatPayV2Config;
	private final MallOrderCouponService orderCouponService;
	private final AppOrderCouponService appOrderCouponService;

	@SneakyThrows
	@Override
	public String payNotify(String xmlParams) {
		log.info("异步通知：{}", xmlParams);

		RLock lock = redisson.getLock(RLockEnum.WXPAY2.getKeyFormat());
		//尝试加锁，最多等待5秒，上锁以后10秒自动解锁
		boolean tryLock = lock.tryLock(5, 10, TimeUnit.SECONDS);
		if (!tryLock) {
			throw new FailException("服务繁忙");
		}

		WxPay2NotifyVO wxPayV2Data = null;
		try {
			wxPayV2Data = XmlParseUtil.domParseWxXml(xmlParams, WxPay2NotifyVO.class);
			if (!wxPayV2Data.isSuccess()) {
				log.error("异步通知失败：{}", wxPayV2Data.getReturnMsg());
				WechatPayV2Response.success();
			}

			String orderNo = wxPayV2Data.getOutTradeNo();
			MallOrderCouponModel orderCoupon = orderCouponService.getOne(new LambdaQueryWrapper<MallOrderCouponModel>()
					.eq(MallOrderCouponModel::getOrderNo, orderNo)
			);
			if (ObjectUtil.isNull(orderCoupon)) {
				log.error("[{}]订单数据丢失，需要手动补销售数据", orderNo);
				return WechatPayV2Response.fail("订单数据丢失");
			}
			if (orderCoupon.getHasPayed()) {
				log.error("[{}]订单重复回调拦截", orderCoupon.getOrderNo());
				return WechatPayV2Response.success();
			}

			//支付完成时间
			Date successTime = DateUtil.parse(wxPayV2Data.getTimeEnd(), DatePattern.PURE_DATETIME_PATTERN);
			orderCouponService.update(new LambdaUpdateWrapper<MallOrderCouponModel>()
					.eq(MallOrderCouponModel::getOrderNo, orderNo)
					.set(MallOrderCouponModel::getPayTime, successTime)
					.set(MallOrderCouponModel::getHasPayed, true)
			);
			orderCoupon.setHasPayed(true);
			orderCoupon.setPayTime(successTime);

			//处理业务
			appOrderCouponService.handleNotifyByWxPay(orderCoupon);
			return WechatPayV2Response.success();
		}
		catch (Exception e) {
			if (Objects.nonNull(wxPayV2Data)) {
				appOrderCouponService.updateTryFail(wxPayV2Data.getOutTradeNo(), OrderEnum.StatusEnum.FAIL.getStatus(), e.getMessage());
			}
			log.error("微信支付回调处理失败:" + e.getMessage(), e);
			return WechatPayV2Response.fail();
		}
		finally {
			lock.unlock();
			log.info("回调锁释放");
		}
	}

	//--统一下单---------------------------------------------------
	@SneakyThrows
	@Override
	public WxPay2UnifiedOrderResponseVO unifiedOrder(WxPay2UnifiedOrderInfoDTO dto) {
		// 拓展属性
		extend(dto);

		WxPay2UnifiedOrderInfoVO unifiedOrderInfo = JSONObject.toJavaObject(JSONObject.parseObject(JSONObject.toJSONString(dto)), WxPay2UnifiedOrderInfoVO.class);

		String format = String.format("%s&key=%s", toUrlString(unifiedOrderInfo), wechatPayV2Config.getApiKey());
		log.info("统一下单-->下单明文 {}", format);
		String sign = SecureUtil.md5(format).toUpperCase();
		unifiedOrderInfo.setSign(sign);

		String xml = XmlParseUtil.toXml(unifiedOrderInfo, "xml");
		log.info("统一下单-->请求参数 ===> {}", xml);
		String responseXml = httpUtil.exchange(
				ApiConstants.WxPayApi.UNIFIED_ORDER_V2.getUrl(),
				ApiConstants.WxPayApi.UNIFIED_ORDER_V2.getMethod(),
				MapBuilder.create(new HashMap<String, String>())
						.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
						.build(),
				null,
				xml,
				ApiConstants.WxPayApi.UNIFIED_ORDER_V2.getResponseClass()
		);
		log.info("统一下单-->返回 ===> {}", responseXml);
		//<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[post数据为空]]></return_msg></xml>

		WxPay2UnifiedOrderResponseVO responseVO = XmlParseUtil.domParseWxXml(responseXml, WxPay2UnifiedOrderResponseVO.class);
		if (!responseVO.isSuccess()) {
			log.info("统一下单-->请求返回 ===> return_msg:{}", responseVO.getReturnMsg());
			throw new FailException(responseVO.getReturnMsg());
		}

		String prepayId = responseVO.getPrepayId();
		log.info("统一下单-->请求返回 ===> prepay_id:{}", prepayId);
		responseVO.setAppKey(wechatPayV2Config.getApiKey());
		responseVO.setAppId(wechatPayV2Config.getAppId());
		responseVO.setSignObj(paySignMsg(prepayId));
		return responseVO;
	}

	/**
	 * 组装【唤起支付请求参数】
	 *
	 * @param prepayId
	 * @return
	 */
	private WxPay2UnifiedOrderResponseVO.SignObj paySignMsg(String prepayId) {
		long timeMillis = System.currentTimeMillis();
		// 公共参数
		// 使用字段appId、timeStamp、nonceStr、package进行签名
		WxPay2UnifiedOrderResponseVO.SignObj signObj = new WxPay2UnifiedOrderResponseVO.SignObj(
				// nonceStr
				String.valueOf(timeMillis),
				// timeStamp
				String.valueOf(timeMillis / 1000),
				// appId
				wechatPayV2Config.getAppId(),
				// package
				String.format("prepay_id=%s", prepayId),
				// signType
				wechatPayV2Config.getSignType()
		);
		signObj.setPaySign(createSign(signObj));
		return signObj;
	}

	/**
	 * 获取加密数据
	 */
	private String createSign(WxPay2UnifiedOrderResponseVO.SignObj params) {
		Map<String, Object> treeMap = JSONObject.parseObject(JSONObject.toJSONString(params),TreeMap.class);

		String signStr = treeMap.entrySet().stream().map(x -> String.format("%s=%s", x.getKey(), x.getValue())).collect(Collectors.joining("&"));
		String format = String.format("%s&key=%s", signStr, wechatPayV2Config.getApiKey());
		log.info("统一下单-->JSAPI调起支付参数明文 {}", format);
		return SecureUtil.md5(format).toUpperCase();
	}

	/**
	 * 统一下单参数拓展
	 *
	 * @param unifiedOrderInfo
	 */
	@SneakyThrows
	private void extend(WxPay2UnifiedOrderInfoDTO unifiedOrderInfo) {
		unifiedOrderInfo.setAppId(wechatPayV2Config.getAppId());
		unifiedOrderInfo.setMchId(wechatPayV2Config.getMchId());
		unifiedOrderInfo.setNonceStr(RandomUtil.randomString(8));
		unifiedOrderInfo.setTerminalIp(InetAddress.getLocalHost().getHostAddress());
		unifiedOrderInfo.setNotifyUrl(wechatPayV2Config.getNotifyUrl());
	}

	/**
	 * 拼接统一下单请求参数
	 *
	 * @param obj
	 * @return
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	private String toUrlString(final Object obj) throws IllegalArgumentException, IllegalAccessException {
		StringBuilder sb = new StringBuilder();
		// 获取f对象对应类中的所有属性域
		Field[] fields = obj.getClass().getDeclaredFields();

		TreeMap<String, Object> fieldList = new TreeMap<>();
		for (Field field : fields) {
			// 对于每个属性，获取属性名
			String fieldName = field.getName();
			field.setAccessible(true);
			// 过滤不处理的对象
			if (Modifier.isStatic(field.getModifiers())) {
				continue;
			}
			Object fieldValue = field.get(obj);
			if (Objects.isNull(fieldValue)) {
				continue;
			}
			fieldList.put(fieldName, fieldValue);
		}
		for (String key : fieldList.keySet()) {
			sb.append(key);
			sb.append("=");
			sb.append(fieldList.get(key));
			sb.append("&");
		}
		return sb.substring(0, sb.length() - 1);
	}
}
